-- ======================================================================
-- Hell Logic Sniffer Addon 
-- 
-- Copyright 2016 Hell-Prototypes
--
-- http://www.hellprototypes.com/
-- ======================================================================

ModuleClass = "LS_PLUGIN"
ModuleName  = "MagCard Track Analyze"
ModuleTip   = "Analyze MagCard track bit stream data"

LAST_TRACK_NO = "MagCard_Track_Analyze_LAST_TRACK_NO"


TRACK1_START =	"1010001"
TRACK1_END   =	"1111100"
TRACK23_START =	"11010"
TRACK23_END   =	"11111"

function shallowcopy(orig)
    local orig_type = type(orig)
    local copy
    if orig_type == 'table' then
        copy = {}
        for orig_key, orig_value in pairs(orig) do
            copy[orig_key] = orig_value
        end
    else -- number, string, boolean, etc
        copy = orig
    end
    return copy
end

function get_current_channel()
	local param = LS_API.GetCommonParameter()
	if #param < 4 then
		return -1
	end

	return param[4]
end

function table_to_string(t, len)
	local str = ""
    for i=1, len do
        str = str .. t[i]
    end
	return str
end

function eat_leading_zero(bit_stream)
	--移除前导0
	while bit_stream[1] == 0 do
		table.remove(bit_stream, 1)
	end
	
	return bit_stream
end

function Track_NO_Detect(bit_stream)
	local lead5 = table_to_string(bit_stream, 5)
	local lead7 = table_to_string(bit_stream, 7)

	if lead5 == TRACK23_START then
	LS_API.DebugMsg("Track2/3 detected")
		return 23
	end
	if lead7 == TRACK1_START then
		LS_API.DebugMsg("Track1 detected")
		return 1
	end

	LS_API.DebugMsg("Track detect error")
	return nil
end

function MagCard_Track1_Analyze(bit_stream)
	LS_API.DebugMsg("MagCard_Track1_Analyze start")
	local bit_idx = 1
	local char_cnt = 0
	local track_str = ""
	local last_ch = ""
	local LRC = 0x0
	while bit_idx < #bit_stream do
		--LS_API.DebugMsg("bit_idx = " .. bit_idx)
		local char_value = 0
		local mask = 1
		local parity = 1
		for i=1, 6 do
			if bit_stream[bit_idx] == 1 then
				char_value = char_value | mask;
				parity = parity ~ 1
			else
				parity = parity ~ 0
			end
			mask = mask << 1
			bit_idx = bit_idx + 1
		end
		char_cnt = char_cnt + 1

		if parity ~= bit_stream[bit_idx] then
			LS_API.DebugMsg("MagCard_Track1_Analyze : Parity error")
			track_str = track_str .. "  (Parity error at char " .. char_cnt .. ")"
			break
		end
		bit_idx = bit_idx + 1
		if last_ch == "?" then
			if LRC ~= char_value then
				track_str = track_str .. "  (LRC error)"
			end
			break
		end
		LRC = LRC ~ char_value
		local ch = string.char(char_value + 0x20)--0x20=space
		track_str = track_str .. ch
		last_ch = ch
	end
	
	LS_API.DebugMsg("MagCard_Track1_Analyze end")
	return track_str
end

function MagCard_Track23_Analyze(bit_stream)
	LS_API.DebugMsg("MagCard_Track23_Analyze start")
	local bit_idx = 1
	local char_cnt = 0
	local track_str = ""
	local last_ch = ""
	local LRC = 0x0
	while bit_idx < #bit_stream do
		--LS_API.DebugMsg("bit_idx = " .. bit_idx)
		local char_value = 0
		local mask = 1
		local parity = 1
		for i=1, 4 do
			if bit_stream[bit_idx] == 1 then
				char_value = char_value | mask;
				parity = parity ~ 1
			else
				parity = parity ~ 0
			end
			mask = mask << 1
			bit_idx = bit_idx + 1
		end
		char_cnt = char_cnt + 1

		if parity ~= bit_stream[bit_idx] then
			LS_API.DebugMsg("MagCard_Track23_Analyze : Parity error")
			track_str = track_str .. "  (Parity error at char " .. char_cnt .. ")"
			break
		end
		bit_idx = bit_idx + 1
		if last_ch == "?" then
			if LRC ~= char_value then
				track_str = track_str .. "  (LRC error)"
			end
			break
		end
		LRC = LRC ~ char_value
		local ch = string.char(char_value + 0x30)--0x30 = 0
		track_str = track_str .. ch
		last_ch = ch
	end
	
	LS_API.DebugMsg("MagCard_Track23_Analyze end")
	return track_str
end


function MagCard_Track_Decode(Bit_Stream)
	--LS_API.ReportPushBack(table_to_string(Bit_Stream, #Bit_Stream))
	Bit_Stream = eat_leading_zero(Bit_Stream)
	--LS_API.ReportPushBack(table_to_string(Bit_Stream, #Bit_Stream))
	
	local track_NO = Track_NO_Detect(Bit_Stream)
	if not track_NO then
		return nil
	end
	
	local track_str = ""
	if track_NO == 1 then
		track_str = MagCard_Track1_Analyze(Bit_Stream)
		track_str = "Track1: " .. track_str
	else
		track_str = MagCard_Track23_Analyze(Bit_Stream)
		if string.len(track_str) <= 46 then
			track_NO = 2
		else
			track_NO = 3
		end
		track_str = "Track".. track_NO .. ": " .. track_str
	end

	return track_str
end

function decoded_data_to_bit(Decoded_Data_Str)
	if string.len(Decoded_Data_Str) > 1 then
		Decoded_Data_Str = string.sub(Decoded_Data_Str, 2, 2)
	end
	return string.byte(Decoded_Data_Str) - string.byte("0")
end

function main ()
	local current_channel = get_current_channel()
	local Decoded_Data = LS_API.GetChannelDecodedData(current_channel)

    if type(Decoded_Data) ~= "table" then
		LS_API.MessageBox("No decoded data found in channel : " .. current_channel)
		return
	end
	if #Decoded_Data < 60 then
		LS_API.MessageBox("No work to do, Bit stream length:" .. #Decoded_Data)
		return
	end
	
	local Bit_Stream = {}
	for i=2, #Decoded_Data, 2 do
		Bit_Stream[#Bit_Stream+1] = decoded_data_to_bit(Decoded_Data[i])
	end

    LS_API.ReportPushBack("")
    LS_API.ReportPushBack("MagCard Track Analyze, Track bit stream length : " .. #Bit_Stream)
	
	local bit_stream_copy = shallowcopy(Bit_Stream)
	for loop = 1, 2 do
		local track_info = MagCard_Track_Decode(bit_stream_copy)
		if track_info then
			LS_API.ReportPushBack(track_info)
			return
		end
		LS_API.DebugMsg("Decoded fail, try reverse bit stream ..")
		--反转
		bit_stream_copy = shallowcopy(Bit_Stream)
		for k=1, #bit_stream_copy do
			local tmp = bit_stream_copy[k]
			bit_stream_copy[k] = bit_stream_copy[#bit_stream_copy - k]
			bit_stream_copy[#bit_stream_copy - k] = tmp
			if k >= #bit_stream_copy/2 then
				break
			end
        end
	end
	LS_API.ReportPushBack("Fail to decoded track bit stream:")
	LS_API.ReportPushBack(table_to_string(Bit_Stream, #Bit_Stream))
end
